package cn.yemuysy.code.check.util;

import cn.yemuysy.code.check.service.impl.TrieNode;

import java.util.Map;

/**
 * 用于Trie树可视化的工具类。
 * <p>
 * 此类提供将Trie树导出为DOT格式的功能，用于可视化展示。
 * DOT是一种图形描述语言，可以被Graphviz等工具处理并生成图形。
 * </p>
 * <p>
 * 可视化Trie树对于以下场景特别有用：
 * <ul>
 *   <li>调试复杂的Trie结构和Aho-Corasick算法</li>
 *   <li>验证单词添加和失败链接构建的正确性</li>
 *   <li>教学演示和文档说明</li>
 *   <li>性能分析，了解树的形状和深度</li>
 * </ul>
 * </p>
 */
public class TrieVisualizationUtil {

    /**
     * 将Trie树导出为DOT格式字符串，用于可视化。
     * <p>
     * 生成的DOT格式字符串可以被Graphviz等工具处理，生成Trie树的可视化图形。
     * 在图中，每个节点都有一个唯一的ID，单词结尾节点会显示完整的单词内容并使用双层边框，
     * 边上标注了对应的字符。图的顶部会显示Trie的类型信息（主/增量，区分/不区分大小写）。
     * </p>
     * <p>
     * 使用示例：
     * <pre>
     * String dot = TrieVisualizationUtil.exportTrieAsDot(trieRoot, true, false);
     * // 将dot字符串保存到文件，然后使用Graphviz处理：
     * // dot -Tpng trie.dot -o trie.png
     * </pre>
     * </p>
     *
     * @param root          Trie树的根节点
     * @param caseSensitive 指定Trie是区分大小写 (true) 还是不区分大小写 (false)
     * @param isDelta       指定Trie是增量Trie (true) 还是主Trie (false)
     * @return DOT格式的字符串，可直接用于Graphviz等工具
     */
    public static String exportTrieAsDot(TrieNode root, boolean caseSensitive, boolean isDelta) {
        if (root == null) {
            return "digraph Trie {\n    // Empty Trie\n}\n";
        }

        assignNodeIds(root, 0);

        StringBuilder dotBuilder = new StringBuilder("digraph Trie {\n");
        dotBuilder.append("    node [shape=circle];\n");
        dotBuilder.append(String.format("    label=\"%s Trie (%s)\";\n", 
                          isDelta ? "Delta" : "Main", 
                          caseSensitive ? "Case Sensitive" : "Case Insensitive"));
        dotBuilder.append("    labelloc=\"t\";\n");

        exportNodeAsDot(root, dotBuilder);
        dotBuilder.append("}\n");
        return dotBuilder.toString();
    }

    /**
     * 为Trie树中的所有节点分配唯一ID。
     * 使用深度优先遍历，递归地为每个节点分配一个唯一的整数ID。
     * 这些ID用于在DOT格式输出中唯一标识每个节点。
     * 
     * @param node 当前处理的节点
     * @param nextId 下一个可用的ID值
     * @return 完成当前子树分配后的下一个可用ID值
     */
    private static int assignNodeIds(TrieNode node, int nextId) {
        node.setNodeId(nextId++);
        for (TrieNode child : node.getChildren().values()) {
            nextId = assignNodeIds(child, nextId);
        }
        return nextId;
    }

    /**
     * 将节点及其子节点递归导出为DOT格式。
     * 此方法为每个节点生成DOT语言的节点定义，并为每个子节点关系生成边定义。
     * 对于表示单词结尾的节点，会使用双层边框并显示单词内容；
     * 对于普通节点，只显示节点ID。
     * 
     * @param node 当前要导出的节点
     * @param dotBuilder 用于构建DOT格式输出的StringBuilder
     */
    private static void exportNodeAsDot(TrieNode node, StringBuilder dotBuilder) {
        String nodeLabel;
        if (node.isEndOfWord()) {
            // 对于单词结尾节点，显示单词内容并使用双层边框
            nodeLabel = String.format("label=\"%d: \\\"%s\\\"\", peripheries=2", 
                                     node.getNodeId(), 
                                     node.getInfringingWord().getWord());
        } else {
            // 对于非结尾节点，只显示节点ID
            nodeLabel = String.format("label=\"%d\"", node.getNodeId());
        }
        dotBuilder.append(String.format("    node%d [%s];\n", node.getNodeId(), nodeLabel));

        for (Map.Entry<Character, TrieNode> entry : node.getChildren().entrySet()) {
            char c = entry.getKey();
            TrieNode child = entry.getValue();
            dotBuilder.append(String.format("    node%d -> node%d [label=\"%s\"];\n", 
                                           node.getNodeId(), 
                                           child.getNodeId(), 
                                           c));
            exportNodeAsDot(child, dotBuilder);
        }
    }
}
